﻿using System.Collections.Generic;
using System.Linq;
using CID.Domain.Development;
using CID.Domain.Exceptions;
using CID.Domain.Operations;
using NailsFramework.Persistence;

namespace CID.Domain.Infrastructure
{
    public abstract class Definition : Model<Definition>, IDefinition
    {
        public virtual int Id { get; private set; }

        private IList<IParameterValue> parameterValues = new List<IParameterValue>();
        public virtual IEnumerable<IParameterValue> ParameterValues { get { return parameterValues; } }

        public virtual IEnumerable<IDefaultParameterValue> DefaultParameterValues
        {
            get { return parameterValues.OfType<IDefaultParameterValue>().ToArray(); }
        }

        public virtual IEnumerable<IEnvironmentParameterValue> EnvironmentParameterValues
        {
            get { return parameterValues.OfType<IEnvironmentParameterValue>().ToArray(); }
        }
        
        protected abstract void CheckValidParameterForTemplate(IParameter parameter);

        public virtual IDefaultParameterValue GetDefaultParameterValue(IParameter parameter)
        {
            return DefaultParameterValues.FirstOrDefault(x => x.Parameter.Equals(parameter));
        }

        public virtual IEnvironmentParameterValue GetEnvironmentParameterValue(IParameter parameter, IEnvironment environment)
        {
            return EnvironmentParameterValues.FirstOrDefault(x => x.Parameter.Equals(parameter) && x.Environment.Equals(environment));
        }
        
        protected virtual IDefaultParameterValue AddDefaultParameterValue(IParameter parameter)
        {
            CheckValidParameterForTemplate(parameter);
            if (GetDefaultParameterValue(parameter) != null) throw new DuplicateParameterValueDefinitionException(parameter);
            var result = new DefaultParameterValue(this, parameter);
            parameterValues.Add(result);
            return result;
        }

        protected virtual void RemoveDefaultParameterValue(IParameter parameter)
        {
            CheckValidParameterForTemplate(parameter);
            var parameterValue = GetDefaultParameterValue(parameter);
            if (parameterValue != null) RemoveDefaultParameterValue(parameterValue);
        }

        private void RemoveDefaultParameterValue(IDefaultParameterValue parameterValue)
        {
            parameterValues.Remove(parameterValue);
        }

        public virtual IEnvironmentParameterValue AddEnvironmentParameterValue(IParameter parameter, IEnvironment environment)
        {
            CheckValidParameterForTemplate(parameter);
            if (GetEnvironmentParameterValue(parameter, environment) != null) throw new DuplicateParameterValueDefinitionException(parameter, environment);
            var result = new EnvironmentParameterValue(this, parameter, environment);
            parameterValues.Add(result);
            return result;
        }

        public virtual void RemoveEnvironmentParameterValue(IParameter parameter, IEnvironment environment)
        {
            CheckValidParameterForTemplate(parameter);
            var parameterValue = GetEnvironmentParameterValue(parameter, environment);
            if (parameterValue != null) RemoveEnvironmentParameterValue(parameterValue);
        }

        public virtual void RemoveEnvironmentParameterValue(IEnvironmentParameterValue parameterValue)
        {
            parameterValues.Remove(parameterValue);
        }

        protected virtual void CleanParameterValuesFor(ITemplate template)
        {
            foreach (var parameterValue in parameterValues.Where(x => (x.Parameter.Template.Equals(template))).ToArray())
                parameterValues.Remove(parameterValue);
        }

        protected virtual void CreateDefaultParameterValuesFor(ITemplate template)
        {
            foreach (var parameter in template.Parameters)
                AddDefaultParameterValue(parameter);
        }
    }

    public class ActivationDeactivationDefinition : Definition, IActivationDeactivationDefinition
    {
        public virtual IComponent Component { get; private set; }
        private IActivationTemplate activationTemplate;
        public virtual IActivationTemplate ActivationTemplate
        {
            get { return activationTemplate; }
            set
            {
                if (!(((activationTemplate == null) && (value == null)) || ((activationTemplate != null) && activationTemplate.Equals(value))))
                {
                    if ((activationTemplate != null) && !activationTemplate.Equals(value))
                        CleanParameterValuesFor(activationTemplate);
                    activationTemplate = value;
                    if (activationTemplate != null)
                        CreateDefaultParameterValuesFor(activationTemplate);
                }
            }
        }

        private IDeactivationTemplate deactivationTemplate;
        public virtual IDeactivationTemplate DeactivationTemplate
        {
            get { return deactivationTemplate; }
            set
            {
                if (!(((deactivationTemplate == null) && (value == null)) || ((deactivationTemplate != null) && deactivationTemplate.Equals(value))))
                {
                    if ((deactivationTemplate != null) && !deactivationTemplate.Equals(value))
                        CleanParameterValuesFor(deactivationTemplate);
                    deactivationTemplate = value;
                    if (deactivationTemplate != null)
                        CreateDefaultParameterValuesFor(deactivationTemplate);
                }
            }
        }

        protected ActivationDeactivationDefinition()
        {
        }

        internal ActivationDeactivationDefinition(IComponent component) : this()
        {
            if (component.ActivationDeactivationDefinition != null) throw new ParentAlreadyAssignedException("Component already has a ActivationDeactivationDefinition");
            Component = component;
        }

        protected override void CheckValidParameterForTemplate(IParameter parameter)
        {
            var template = parameter.Template;
            if (!template.Equals(ActivationTemplate) && !template.Equals(DeactivationTemplate))
                throw new InvalidParameterValueDefinitionException("The Parameter belongs to a Template that is not the Activation nor the Deactivation templates of this Definition");
        }

        public virtual IEnumerable<IParameterValue> GetActivationParamaterValues(IEnvironment environment)
        {
            var result = new List<IParameterValue>();
            foreach (IParameter parameter in ActivationTemplate.Parameters)
            {
                IParameterValue parameterValue = GetEnvironmentParameterValue(parameter, environment);
                if (parameterValue == null) parameterValue = GetDefaultParameterValue(parameter);
                if (parameterValue != null) result.Add(parameterValue);
            }

            return result;
        }

        public virtual IEnumerable<IParameterValue> GetDeactivationParamaterValues(IEnvironment environment)
        {
            var result = new List<IParameterValue>();
            foreach (IParameter parameter in DeactivationTemplate.Parameters)
            {
                IParameterValue parameterValue = GetEnvironmentParameterValue(parameter, environment);
                if (parameterValue == null) parameterValue = GetDefaultParameterValue(parameter);
                if (parameterValue != null) result.Add(parameterValue);
            }

            return result;
        }
    }

    public class DeploymentDefinition : Definition, IDeploymentDefinition
    {
        public virtual IVersion Version { get; private set; }
        private IDeploymentTemplate deploymentTemplate;
        public virtual IDeploymentTemplate DeploymentTemplate {
            get { return deploymentTemplate; }
            set {
                if (!(((deploymentTemplate == null) && (value == null)) || ((deploymentTemplate != null) && deploymentTemplate.Equals(value))))
                {
                    if ((deploymentTemplate != null) && !deploymentTemplate.Equals(value))
                        CleanParameterValuesFor(deploymentTemplate);
                    deploymentTemplate = value;
                    if (deploymentTemplate != null)
                        CreateDefaultParameterValuesFor(deploymentTemplate);
                }
             }
        }

        protected DeploymentDefinition()
        { 
        }

        internal DeploymentDefinition(IVersion version) : this()
        {
            if (version.DeploymentDefinition != null) throw new ParentAlreadyAssignedException("Version already has a DeploymentDefinition");
            Version = version;
        }

        protected override void CheckValidParameterForTemplate(IParameter parameter)
        {
            var template = parameter.Template;
            if (!template.Equals(DeploymentTemplate))
                throw new InvalidParameterValueDefinitionException("The Parameter belongs to a Template that is not the Deployment Template of this Definition");
        }

        public virtual IEnumerable<IParameterValue> GetDeploymentParamaterValues(IEnvironment environment)
        {
            var result = new List<IParameterValue>();
            foreach (IParameter parameter in DeploymentTemplate.Parameters)
            {
                IParameterValue parameterValue = GetEnvironmentParameterValue(parameter, environment);
                if (parameterValue == null) parameterValue = GetDefaultParameterValue(parameter);
                if (parameterValue != null) result.Add(parameterValue);
            }

            return result;
        }
    }
}
